﻿using System;
using System.Threading;
using System.Threading.Tasks;


namespace DailyFx.Common
{
	public class RingBuffer<T>
	{
		/// <summary>
		///		the actual array for objects enlisted .
		/// </summary>
		private readonly T[] _buffer;

		/// <summary>
		///		the capacity of this ring buffer .
		/// </summary>
		private readonly int _bufferLength = 1024 ;

		/// <summary>
		///		the index for reading .
		/// </summary>
		private int _head = 0;

		/// <summary>
		///		the index for writing .
		/// </summary>
		private int _tail = 0;

		/// <summary>
		///		Indicates whether suspended for writing .
		/// </summary>
		private bool _writingSuspended;

		/// <summary>
		///		sync root .
		/// </summary>
		private readonly object _lock = new object();

		/// <summary>
		/// 
		/// </summary>
		public RingBuffer ( int n )
        {
			this._buffer = new T[ n];
            this._bufferLength = n;
        }

		/// <summary>
		///		Gets the number of items in this buffer .
		/// </summary>
		public int Count
		{
			get 
			{
				int count;
				lock (_lock)
				{
					count = ( ( _tail % _bufferLength) - _head );
				}
				return ( count < 0) ? (-1) * count : count;
			}
		}

		/// <summary>
		///		Checks if this is empty buffer .
		/// </summary>
		private bool Empty
		{
			get 
			{
				return ( this._head == this._tail );
			}
		}

		/// <summary>
		///		Checks if this is full 
		/// </summary>
		private bool Full
		{
			get { return  (( _tail + 1) % _bufferLength) == _head; }
		}

		/// <summary>
		///		Adds new item to the back of this buffer .
		/// </summary>
		public void Push ( T item )
		{
			lock ( _lock )
			{
				while ( this.Full || this._writingSuspended)
				{
					Monitor.Wait( _lock );
				}

				this._buffer [_tail] = item;
				this._tail++;
				this._tail%= this._bufferLength;

				Monitor.Pulse ( _lock);
			}
		}

		/// <summary>
		///		Gets the first item form this buffer .
		/// </summary>
		public T Peek()
		{
			T item;

			lock (_lock)
			{
				if ( this.Empty )
				{
					return default (T);
				}

				item = this._buffer[ this._head ];
			}

			return item;
		}

		/// <summary>
		///		Removes the first item from the buffer .
		/// </summary>
		public T Pop ()
		{
			T item;
			lock ( _lock )
			{
				while ( this.Empty )
				{
					Monitor.Wait( _lock);
				}
				item = _buffer [ _head ];
				this._head ++;
				this._head %= this._bufferLength;

				Monitor.Pulse(_lock);
			}

			return item;
		}

		/// <summary>
		/// 
		/// </summary>
		private void Clear ()
		{
			this._head = 0;
			this._tail = 0;

			// Clear the contents .
			Array.Clear( _buffer, 0, _bufferLength );
		}


		/// <summary>
		///		Suspends the buffer from writing item(s) in .
		/// </summary>
		public void TryBlock ()
		{
			if ( this._writingSuspended ) 
				return;

			Task.Factory.StartNew(  () =>
			{
				_writingSuspended = true;
				while ( true)
				{
					lock (_lock)
					{
						if (!this.Empty) 
							continue;

						this._writingSuspended = false;

						Monitor.PulseAll(_lock);
					}
					return;
				}
			});
		}

	}
}
